use crate::field::{VddFieldRef, VddFieldType};
use crate::id_factory::ID_FACTORY;
use crate::proj::DataSourceTrait;
use crate::{datetime_format, i18n, FieldId, ProjectRsaHelper};
use crate::{Astr, UserId};
use chrono::{Local, NaiveDateTime};
use serde::{Deserialize, Serialize};
use serde_json::Value;
use std::collections::HashMap;
use std::net::IpAddr;
use std::str::FromStr;
use std::sync::Arc;

#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
pub enum DataStoreEngine {
    #[cfg(feature = "postgres")]
    Postgres,
    Mysql,
}

impl DataStoreEngine {
    pub fn from_str(s: &str) -> Self {
        if s.eq_ignore_ascii_case("mysql") {
            return DataStoreEngine::Mysql;
        }
        #[cfg(feature = "postgres")]
        if s.eq_ignore_ascii_case("postgres") {
            return DataStoreEngine::Postgres;
        }
        panic!("{}", i18n!(1106, s))
    }
}

#[derive(Debug, Serialize, Deserialize, PartialEq)]
#[serde(rename_all = "camelCase")]
pub struct DataStore {
    pub store_engine: DataStoreEngine,
    pub host: IpAddr,
    pub port: usize,
    pub user: Astr,
    pub password: Astr,
    pub store_family_name: Astr,        //数据库名
    pub remote_key: Astr,               //表名
    pub primary_key: Option<Astr>,      // 单一主键列名 bigint
    pub uts_key: Option<Astr>,          //更新时间戳列名 timestamp
    pub logic_useful_sql: Option<Astr>, // 逻辑是否有效判断条件
}

impl TryFrom<&Value> for DataStore {
    type Error = Astr;

    fn try_from(value: &Value) -> Result<Self, Self::Error> {
        Ok(Self {
            store_engine: DataStoreEngine::from_str(value["engine"].as_str().unwrap()),
            host: IpAddr::from_str(value["host"].as_str().unwrap()).unwrap(),
            port: value["port"].as_u64().unwrap() as usize,
            user: Arc::from(value["user"].as_str().unwrap()),
            password: Arc::from(value["password"].as_str().unwrap()),
            store_family_name: Arc::from(value["dbName"].as_str().unwrap()),
            remote_key: value["tbName"].as_str().unwrap().into(),
            primary_key: value["pkCol"].as_str().map(|s| s.into()),
            uts_key: value["utsCol"].as_str().map(|s| s.into()),
            logic_useful_sql: value["luSql"].as_str().map(|s| s.into()),
        })
    }
}

impl DataStore {
    pub fn new(j: &Value, pri_key: &str, pub_key: &str) -> Result<Self, Astr> {
        let pwd = j["password"].as_str().unwrap();
        let rsa = ProjectRsaHelper::init(pri_key, pub_key);
        let password = rsa.decode_b64str(pwd);
        if password.is_err() {
            return Err(i18n!(1007, &password.unwrap_err()));
        }
        Self::try_from(j).map(|mut ds| {
            ds.password = password.unwrap().into();
            ds
        })
    }

    /// 从配置信息中生成默认的模型名称
    pub fn default_model_name(&self) -> String {
        format!(
            "{:?}{}{}",
            self.store_engine, self.store_family_name, self.remote_key
        )
    }
    ///
    pub fn same_as(&self, other: &Self) -> bool {
        if self.store_engine != other.store_engine {
            return false;
        }
        if self.host != other.host {
            return false;
        }
        if self.port != other.port {
            return false;
        }
        if self.store_family_name != other.store_family_name {
            return false;
        }
        if self.remote_key != other.remote_key {
            return false;
        }
        true
    }
}

impl DataSourceTrait for DataStore {
    fn to_url(&self) -> Astr {
        let url = match self.store_engine {
            #[cfg(feature = "postgres")]
            DataStoreEngine::Postgres => {
                format!(
                    "postgresql://{}:{}@{}:{}/{}",
                    self.user,
                    self.password,
                    self.host.to_string(),
                    self.port,
                    self.store_family_name
                )
            }
            DataStoreEngine::Mysql => {
                format!(
                    "mysql://{}:{}@{}:{}/{}",
                    self.user,
                    self.password,
                    self.host.to_string(),
                    self.port,
                    self.store_family_name
                )
            }
        };
        url.into()
    }
}

#[derive(Debug, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct VddModel {
    pub id: u64,
    pub uid: UserId,
    pub name: Astr,
    pub memo: Option<Astr>,
    #[serde(with = "datetime_format")]
    pub update_ts: NaiveDateTime,
    pub allow_visit: bool,
    pub store: Option<Arc<DataStore>>,
}

pub type VddModelRef = Arc<VddModel>;

impl VddModel {
    pub fn new(name: &str, owner_id: &UserId, memo: &str, allow_visit: bool) -> Self {
        Self {
            id: ID_FACTORY.lock().unwrap().lazy_generate(),
            uid: owner_id.to_owned(),
            name: Arc::from(name),
            memo: if memo.is_empty() {
                None
            } else {
                Some(Arc::from(memo))
            },
            update_ts: Local::now().naive_local(),
            allow_visit,
            store: None,
        }
    }
    pub fn vec_to_map(model_vec: Vec<Arc<Self>>) -> HashMap<u64, Arc<Self>> {
        let mut model_map: HashMap<u64, Arc<Self>> = HashMap::new();
        model_vec.iter().for_each(|x| {
            model_map.insert(x.id, x.clone());
        });
        model_map
    }
    /// 判定一个模型作者是否是某个用户
    pub fn belong_to(&self, u_id: &UserId) -> bool {
        &self.uid == u_id
    }
}

/// 一个模型的全部定义信息，用于传参
#[derive(Debug)]
pub struct VddSchema {
    pub model: VddModelRef,
    pub fields: Vec<VddFieldRef>,
    pub tags: HashMap<u64, Astr>,
}

impl VddSchema {
    pub fn get_model_tag(&self) -> Astr {
        if self.tags.contains_key(&self.model.id) {
            return self.tags.get(&self.model.id).unwrap().clone();
        }
        panic!(
            "{}",
            i18n!(1203, &self.model.name, &self.model.id.to_string())
        );
    }
    pub fn get_field_tag(&self, fid: &FieldId) -> Astr {
        if self.tags.contains_key(&fid.0) {
            return self.tags.get(&fid.0).unwrap().clone();
        }
        panic!("{}", i18n!(1203, &self.model.name, &fid.0.to_string()));
    }
    /// 获取一个远端存储记录的模型的对应表或视图名称
    pub fn get_model_remote_key(&self) -> Option<Astr> {
        let store = self.model.store.clone()?;
        Some(store.remote_key.clone())
    }
    /// 获取一个远端存储记录的模型的对应表的更新时间戳列名
    pub fn get_model_uts_key(&self) -> Option<Astr> {
        let store = self.model.store.clone()?;
        store.uts_key.clone()
    }
    /// 获取一个远端存储记录的模型的对应表的单列主键列名
    pub fn get_model_primary_key(&self) -> Option<Astr> {
        let store = self.model.store.clone()?;
        store.primary_key.clone()
    }
    /// 获取一个远端存储记录的模型的有效记录辨别方式
    pub fn get_model_logic_useful_sql(&self) -> Option<Astr> {
        let store = self.model.store.clone()?;
        store.logic_useful_sql.clone()
    }
    /// 获取一个模型的对应表的主键字段id
    pub fn get_model_remote_primary_key_as_fid(&self) -> Option<FieldId> {
        let rk = self.get_model_uts_key()?;
        let mut r: Option<FieldId> = None;
        self.tags.iter().for_each(|kv| {
            if kv.1 == &rk {
                r = Some(FieldId(*kv.0))
            }
        });
        r
    }
    /// 从定义中查找一个字段
    pub fn try_get_field(&self, field_id: &FieldId) -> Result<VddFieldRef, Astr> {
        self.fields
            .iter()
            .find(|x| x.id == *field_id)
            .map(|x| x.clone())
            .ok_or("not found field".into())
    }
    /// 获取一个模型所有的消费其他模型记录的信息
    pub fn consumes(&self) -> Vec<(u64, FieldId)> {
        let mut r = Vec::<(u64, FieldId)>::new();
        self.fields
            .iter()
            .for_each(|field| match field.field_type.as_ref() {
                VddFieldType::FtModel {
                    provider: lm_id,
                    display_field: lf_id,
                } => {
                    r.push((*lm_id, lf_id.clone()));
                }
                _ => {}
            });
        r
    }
    /// 获取A模型中消费了B模型记录的字段 支持一个A模型中多个字段同时消费 虽然理论上不能这样干
    pub fn get_field_by_provider(
        &self,
        provide_m_id: u64,
        provide_f_id: Option<FieldId>,
    ) -> Option<VddFieldRef> {
        println!(
            "get_field_by_provider {:?} {:?}",
            provide_m_id, provide_f_id
        );
        let mut r = Option::<VddFieldRef>::None;
        self.fields
            .iter()
            .for_each(|f| match f.field_type.as_ref() {
                VddFieldType::FtModel {
                    provider,
                    display_field,
                } => {
                    let accept = if *provider == provide_m_id {
                        if let Some(p_lf_id) = provide_f_id.clone() {
                            &p_lf_id == display_field
                        } else {
                            true
                        }
                    } else {
                        false
                    };
                    if accept {
                        r = Some(f.clone());
                    }
                }
                _ => {}
            });
        r
    }
    /// 判断这个模型是否已经完全设定了标记
    pub fn all_taged(&self) -> bool {
        self.tags.len() >= (self.fields.len() + 1)
    }
}
